package dst.ass3.aop.impl;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Logger;
import dst.ass3.aop.IPluginExecutable;
import dst.ass3.aop.IPluginExecutor;

/**
 * It has to monitor (i.e.,
 * repeatedly list the contents of) several directories to detect whether new .jar les were copied to
 * these directories or existing .jar les were modied. (Note: In addition to monitoring for changes,
 * also check the directories once when initializing your application. Note 2: Monitoring of sub-
 * directories is not required.) The executor then scans the le and looks for classes that implement
 * the IPluginExecutable interface. If some plugin executable is found, the executor spawns a new
 * thread and calls its execute method. For this, you should be using a thread pool.
 */

public class PluginExecutor implements IPluginExecutor{

	private final static Logger LOGGER = Logger.getLogger(PluginExecutor.class.getName());
	
    private Set<File> directorySet = Collections.synchronizedSet(new HashSet<File>());
    private ExecutorService executorService = Executors.newCachedThreadPool();
    private Timer scanningTimer = new Timer();
    private DirectoryScanner directoryScanner;

    public PluginExecutor() {
    	directoryScanner = new DirectoryScanner(this);
    }

    @Override
    public void monitor(File dir) {
    	directorySet.add(dir);
    }

    @Override
    public void stopMonitoring(File dir) {
    	directorySet.remove(dir);
    }

    @Override
    public void start() {
    	//scan every 1000 seconds the given directories
    	scanningTimer.schedule(new TimerTask() {
            @Override
            public void run() {
            	directoryScanner.scanDirectories(directorySet);
            }
        }, 0, 1000);
    }

    @Override
    public void stop() {
    	scanningTimer.cancel();
    }

    /**
     * is called from the DirectoryScanner and Execute the Jar File
     * @param file
     */
	public void onNewJar(File file) {
		JarFile jarFile = null;
        URLClassLoader cl = null;
        
        if(file!=null){
	        try {
	        	LOGGER.info("Execute JAR:"+ file.getName());
	        	
	        	//Make a JarFile out of normal File
	            jarFile = new JarFile(file);
	            Enumeration<JarEntry> jarEntries = jarFile.entries();  //get Entries
	
	            //Create Url and load Classes <- http://docs.oracle.com/javase/7/docs/api/java/net/URLClassLoader.html          
	            URL url = file.toURI().toURL();
	            URL[] urls = new URL[]{url};
	            
	            cl = URLClassLoader.newInstance(urls);
	
	            while (jarEntries.hasMoreElements()) {
	                JarEntry jarEntry = jarEntries.nextElement();
	                
	                //just work with class files
	                if(jarEntry.isDirectory() || !jarEntry.getName().endsWith(".class")){ 
	                    continue;  //is not a class file
	                }
	                else{
		                LOGGER.info("Entry Name:"+ jarEntry.getName());
		                
		                //change to classname
		                //e.g. dst/ass3/aop/sample/SimplePluginExecutable.class
		                //to dst.ass3.aop.sample.SimplePluginExecutable
		                String className = jarEntry.getName().substring(0, jarEntry.getName().length() - ".class".length()).replace('/', '.');
		                Class<?> clazz = cl.loadClass(className);
		                
		                LOGGER.info("Class Name:"+ className);
		
		                //Determines if the class or interface represented by this Class object is either the same as, 
		                //or is a superclass or superinterface of, the class or interface represented by the specified Class parameter.
		                if (!IPluginExecutable.class.isAssignableFrom(clazz)) {  //is not a IPluginExecutable
		                    continue;
		                }
		                else{
		                	//Instance to IPluginExecutable
		                	final IPluginExecutable instance = (IPluginExecutable)clazz.newInstance();
			                LOGGER.info("Instance execute:"+ instance.getClass().getName());
			                
			                //run Class in own thread
			                executorService.execute(new Runnable() {
			                    @Override
			                    public void run() {
			                        instance.execute();
			                    }
			                });
			                
		                }
	                }
	            }
	
	            cl.close();
	        } catch (IOException e) {
	            e.printStackTrace();
	        } catch (ClassNotFoundException e) {
	            e.printStackTrace();
	        } catch (InstantiationException e1) {
	            e1.printStackTrace();
	        } catch (IllegalAccessException e1) {
	            e1.printStackTrace();
			} finally {
				try {
					if (jarFile != null) {
						jarFile.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
				try {
					if (cl != null) {
						cl.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
        }
	}

}
